#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>

#include "net_hdr.h"
#include "types.h"
#include "pk_buff.h"
#include "ap_assert.h"
#include "ap_printf.h"
#include "timer.h"
#include "user_packet.h"


/* ------------------------------------------------------------- */

/* reassemble large packet buffer */

#define  REASSEMBLE_TIMEOUT    5
#define  REASS_BUF_MAX_COUNT   4096 * 16

struct mem_pool *reass_buf_pool;
volatile int reass_buf_reclaim;

void reclaim_reassemble_buf(struct reassemble_list *list, u32 tm) 
{
    struct list_head *iter, *next;
    list_for_each_safe(iter, next, &list->head) {
        struct reassemble_buf *buf = list_entry(iter, 
                           struct reassemble_buf, link);
        if (tm >= buf->timeout) {
            list_del(iter);
            free_reassemble_buf(buf);
            list->count--;
        }
    }
}

int push_segment(struct reassemble_list *list, struct pk_buff *pkb)
{
    struct pkt_info *pktinfo = (struct pkt_info *)pkb->cb;
    struct reassemble_buf *buf = NULL;
    
    reclaim_reassemble_buf(list, TIMESTAMP);

    if (pkb->res_buf) {
        list_add_tail(&pkb->res_buf->link, &list->head);
        pkb->res_buf->timeout = TIMESTAMP + REASSEMBLE_TIMEOUT;

        /* 
         * this pkt should not go further,
         * make coredump for debug 
         */
        pkb->head = pkb->data = NULL; 
        pkb->total = pkb->len = 0;
        pkb->res_buf = NULL;
        return 0;
    }

    buf = alloc_reassemble_buf();
    if (!buf)
        return -1;

    /* copy full content of the first packet */
    
    memcpy(buf->data, pkb->head, pkb->total);
    buf->len = pkb->total;
	
#ifdef CONFIG_NETWORK_WCDMA
    buf->iphdr = (struct iphdr *)(buf->data + 
            ((u8 *)pkb->iphdr - pkb->head));
    buf->gtphdr = (struct gtphdr *)(buf->data + 
            ((u8 *)pkb->gtphdr - pkb->head));
#endif
    
    buf->iphdr_u = (struct iphdr *)(buf->data + 
            ((u8 *)pktinfo->iphdr - pkb->head));
    buf->tcphdr_u = (struct tcphdr *)(buf->data +
            ((u8 *)pktinfo->tcphdr - pkb->head));
    buf->next_seq = ntohl(pktinfo->tcphdr->seq) + pkb->len; 
    
    buf->timeout = TIMESTAMP + REASSEMBLE_TIMEOUT;
    buf->pkts = 1;
    
    list_add_tail(&buf->link, &list->head);
    list->count++;
    return 0;
}

int reassemble_segment(struct reassemble_list *list,
                           struct pk_buff *pkb)
{
    struct pkt_info *pktinfo = (struct pkt_info *)pkb->cb;
    struct reassemble_buf *buf = NULL;
    struct list_head  *iter, *next;
    
    register u32 daddr = pktinfo->iphdr->daddr;
    register u32 saddr = pktinfo->iphdr->saddr;
    register u16 dport = pktinfo->tcphdr->dest;
    register u16 sport = pktinfo->tcphdr->source;
    register u32 seq   = ntohl(pktinfo->tcphdr->seq);

    reclaim_reassemble_buf(list, TIMESTAMP);
    
    list_for_each_safe(iter, next, &list->head) {
        buf = list_entry(iter, struct reassemble_buf, link);
        if (buf->next_seq == seq && 
            buf->iphdr_u->daddr == daddr && 
            buf->iphdr_u->saddr == saddr &&
            buf->tcphdr_u->dest == dport && 
            buf->tcphdr_u->source == sport) {

            list_del(iter);
            list->count--;

            if ((buf->len + pkb->len) < HUGE_BUFFER_LEN) {
                memcpy(buf->data + buf->len, pkb->data, pkb->len);
                buf->pkts++;
                buf->len += pkb->len;
                buf->next_seq = buf->next_seq + pkb->len;

                pktinfo->iphdr = buf->iphdr_u;
                pktinfo->tcphdr = buf->tcphdr_u;

#ifdef CONFIG_NETWORK_WCDMA
                pkb->iphdr = buf->iphdr;
                pkb->gtphdr = buf->gtphdr;
#endif
                pkb->head = buf->data;
                pkb->data = ((u8 *)buf->tcphdr_u) + (buf->tcphdr_u->doff * 4);

                pkb->total = buf->len;
                pkb->len   = buf->len - (pkb->data - pkb->head);

                pkb->res_buf = buf;
                return 0;

            } else {
                /*
                 * FIX ME!!, 
                 * alloc larger buffer to reduce packet loss
                 */
                free_reassemble_buf(buf);
                return -1;
            }
        }
    }
    return -1;
}


int pkt_reassemble_init()
{
    reass_buf_pool = mem_pool_create(REASS_BUF_MAX_COUNT >> 1, REASS_BUF_MAX_COUNT, 
          REASS_BUF_MAX_COUNT >> 3, sizeof(struct reassemble_buf), 0, "reassemble_buf");
    return reass_buf_pool ? 0 : -1;
}

void pkt_reassemble_clean()
{
    if (reass_buf_pool)
        mem_pool_destroy(reass_buf_pool);
}



